﻿namespace JoinBox.Basal;

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public class MouseHook
{
    /// <summary>
    /// 鼠标按下事件
    /// </summary>
    public event MouseEventHandler? MouseDown;
    /// <summary>
    /// 松开鼠标事件
    /// </summary>
    public event MouseEventHandler? MouseUp;
    /// <summary>
    /// 鼠标移动事件
    /// </summary>
    public event MouseEventHandler? MouseMove;
    /// <summary>
    /// 鼠标滚轮事件
    /// </summary>
    public event MouseEventHandler? MouseWheel;
    /// <summary>
    /// 鼠标单击事件
    /// </summary>
    public event EventHandler? Click;
    /// <summary>
    /// 鼠标双击事件
    /// </summary>
    public event EventHandler? DoubleClick;


    static int _hHook = 0; // 声明键盘钩子处理的初始值

    /// <summary>
    /// 钩子回调
    /// </summary>
    /// <remarks>
    /// 不要试图省略此变量,否则将会导致GC变量池满后释放<br/>
    /// 提示:激活 CallbackOnCollectedDelegate 托管调试助手(MDA)
    /// </remarks>
    readonly WindowsAPI.HookProc? _hookProc;

    /// <summary>
    /// 安装鼠标钩子
    /// </summary>
    /// <param name="IsSystemHook">系统钩子true,线程钩子false</param>
    public MouseHook(bool IsSystemHook = true)
    {
        // 安装钩子
        if (_hHook != 0)
            return;

        _hookProc = new(HookProcz);

        if (IsSystemHook)
        {
            // var hinstance = LoadLibrary("User32");
            // var hinstance = Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0]);
            // var hinstance = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);

            if (_hookProc == null)
                return;

            var gcp = System.Diagnostics.Process.GetCurrentProcess();
            if (gcp == null)
                return;
            var name = gcp?.MainModule?.ModuleName;
            if (name == null)
                return;
            var moduleName = WindowsAPI.GetModuleHandle(name);
            _hHook = WindowsAPI.SetWindowsHookEx(HookType.WH_MOUSE_LL,
                                _hookProc, moduleName, 0/*系统钩子*/);
        }
        else
        {
            _hHook = WindowsAPI.SetWindowsHookEx(HookType.WH_KEYBOARD,
                                _hookProc, IntPtr.Zero, WindowsAPI.GetCurrentThreadId()/*线程钩子*/);
        }

        // 如果设置钩子失败
        if (_hHook == 0)
        {
            Dispose();
            throw new Exception("安装鼠标钩子失败");
        }
    }

    /// <summary>
    /// 中断回调阻断输入
    /// </summary>
    public bool IsHookBreak = false;

    // 记录鼠标按下右键
    public MouseButtons button = MouseButtons.None;

    /// <summary>
    /// 这里可以添加自己想要的信息处理
    /// <a href="https://www.cnblogs.com/yfceshi/p/6791943.html">相关链接</a>
    /// </summary>
    /// <param name="nCode"></param>
    /// <param name="wParam"></param>
    /// <param name="lParam"></param>
    /// <returns></returns>
    int HookProcz(int nCode, int wParam, IntPtr lParam)
    {
        var wPa = (WM)wParam;

        // int ret = 0;// 0是不阻断,1是阻断
        var flag = MouseDown is not null 
                   || MouseUp is not null
                   || MouseMove is not null
                   || MouseWheel is not null
                   || Click is not null 
                   || DoubleClick is not null;
        if (nCode > -1 && flag)
        {
            var button = MouseButtons.None;
            int clickCount = 0;

            bool down = false;
            bool up = false;
            bool ck = false;
            switch (wPa)
            {
                case WM.WM_LBUTTONDOWN:
                    button = MouseButtons.Left;
                    clickCount = 1;
                    down = true;
                    ck = true;
                    break;
                case WM.WM_LBUTTONUP:
                    button = MouseButtons.Left;
                    clickCount = 1;
                    up = true;
                    break;
                case WM.WM_LBUTTONDBLCLK:
                    button = MouseButtons.Left;
                    clickCount = 2;
                    ck = true;
                    break;
                case WM.WM_RBUTTONDOWN:
                    button = MouseButtons.Right;
                    clickCount = 1;
                    down = true;
                    ck = true;
                    break;
                case WM.WM_RBUTTONUP:
                    button = MouseButtons.Right;
                    clickCount = 1;
                    up = true;
                    break;
                case WM.WM_RBUTTONDBLCLK:
                    button = MouseButtons.Right;
                    clickCount = 2;
                    ck = true;
                    break;

                case WM.WM_MBUTTONDOWN:
                    button = MouseButtons.Middle;
                    clickCount = 1;
                    ck = true;
                    break;

                case WM.WM_MOUSEWHEEL:// 滚轮
                    break;
                case WM.WM_MOUSEMOVE:// 移动

#if false
                // 假设想要限制鼠标在屏幕中的移动区域能够在此处设置
                // 后期须要考虑实际的x、y的容差
                if (!Screen.PrimaryScreen.Bounds.Contains(e.X, e.Y))
                    // return 1;

                if (button == MouseButtons.Left)
                {
                    GetCursorPos(out POINT pt);
                    // 防止频繁获取导致出错
                    if (pt0ld.Leng(pt) > 20)
                        pt0ld = pt;
                }
#endif
                break;
            }

            // 从回调函数中得到鼠标的信息
            var mouseMsg = Marshal.PtrToStructure(lParam, typeof(WindowsAPI.MouseMsg)) as WindowsAPI.MouseMsg;
            if (mouseMsg is not null)
            {
                var e = new MouseEventArgs(button, clickCount, mouseMsg.pt.X, mouseMsg.pt.Y, 0);
                if (down)
                    MouseDown?.Invoke(this, e);
                if (up)
                    MouseUp?.Invoke(this, e);
                if (ck)
                    Click?.Invoke(this, e);
                if (clickCount == 2)
                    DoubleClick?.Invoke(this, e);
                MouseMove?.Invoke(this, e);
                MouseWheel?.Invoke(this, e);
            }
        }

        return WindowsAPI.CallNextHookEx(_hHook, nCode, wParam, lParam);
    }


    #region IDisposable接口相关函数
    public bool IsDisposed { get; private set; } = false;

    /// <summary>
    /// 手动调用释放
    /// </summary>
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// 析构函数调用释放
    /// </summary>
    ~MouseHook()
    {
        Dispose(false);
    }

    protected virtual void Dispose(bool disposing)
    {
        // 不重复释放,并设置已经释放
        if (IsDisposed) return;
        IsDisposed = true;


        bool retKeyboard = true;
        if (_hHook != 0)
        {
            retKeyboard = WindowsAPI.UnhookWindowsHookEx(_hHook);
            _hHook = 0;
        }
        if (!retKeyboard)
            throw new Exception("Dispose鼠标钩子失败!");
    }
    #endregion
}
